import abc
import datetime
import functools
import traceback

from coin.exchange.base.kr_rest.currency_constants import (
    BCHABC_BCHSV_FORK_TIME,
    BCHA_BCHN_FORK_TIME)


class FromStrCurrencyMixin(object):
  @classmethod
  def FromStrCurrency(cls, currency, current_datetime=None):
    current_datetime = current_datetime or datetime.datetime.now()
    if currency == 'BCH':
      assert current_datetime < BCHABC_BCHSV_FORK_TIME, current_datetime
    elif currency == 'BCHABC':
      # TODO(chensili): 
      # add assert current_datetime <= XXX when we finish BCHABC migration.
      assert BCHABC_BCHSV_FORK_TIME <= current_datetime, current_datetime
    elif currency == 'BCHSV':
      assert BCHABC_BCHSV_FORK_TIME <= current_datetime, current_datetime
    elif currency in ('BCHA', 'BCHN'):
      assert BCHA_BCHN_FORK_TIME <= current_datetime, current_datetime
    return cls.FromStrCurrencyImpl(currency, current_datetime)

  @classmethod
  def FromStrNativeCurrency(cls, currency, current_datetime=None):
    current_datetime = current_datetime or datetime.datetime.now()
    return cls.FromStrNativeCurrencyImpl(currency, current_datetime)

  @classmethod
  @abc.abstractmethod
  def FromStrCurrencyImpl(cls, currency, current_datetime):
    raise NotImplementedError()

  @classmethod
  @abc.abstractmethod
  def FromStrNativeCurrencyImpl(cls, currency, current_datetime):
    raise NotImplementedError()

  @classmethod
  @functools.lru_cache(maxsize=256)
  def FromStrCurrencyNothrow(cls, currency, current_datetime=None):
    assert isinstance(currency, str), currency
    try:
      return cls.FromStrCurrency(currency, current_datetime)
    except (ValueError, AssertionError) as e:
      return None

  @classmethod
  @functools.lru_cache(maxsize=256)
  def FromStrNativeCurrencyNothrow(cls, currency, current_datetime=None):
    assert isinstance(currency, str), currency
    try:
      return cls.FromStrNativeCurrency(currency, current_datetime)
    except (ValueError, AssertionError) as e:
      return None

  @classmethod
  @functools.lru_cache(maxsize=256)
  def FromStr(cls, currency, current_datetime=None):
    assert isinstance(currency, str), currency
    errstr = ""
    try:
      return cls.FromStrCurrency(currency, current_datetime)
    except (ValueError, AssertionError) as e:
      errstr += traceback.format_exc()
    try:
      return cls.FromStrNativeCurrency(currency, current_datetime)
    except (ValueError, AssertionError) as e:
      errstr += traceback.format_exc()
    raise ValueError("Unable to parse: %s, %s, %s\n%s" % (currency, current_datetime, cls, errstr))

  @classmethod
  @functools.lru_cache(maxsize=256)
  def FromStrNothrow(cls, currency, current_datetime=None):
    try:
      return cls.FromStr(currency, current_datetime)
    except (ValueError, AssertionError) as e:
      return None


class FromStrProductMixin(object):
  @classmethod
  def FromStrProduct(cls, product, current_datetime=None):
    current_datetime = current_datetime or datetime.datetime.now()
    return cls.FromStrProductImpl(product, current_datetime)

  @classmethod
  def FromStrNativeProduct(cls, product, current_datetime=None):
    current_datetime = current_datetime or datetime.datetime.now()
    return cls.FromStrNativeProductImpl(product, current_datetime)

  @classmethod
  @abc.abstractmethod
  def FromStrProductImpl(cls, product, current_datetime):
    raise NotImplementedError()

  @classmethod
  @abc.abstractmethod
  def FromStrNativeProductImpl(cls, product, current_datetime):
    raise NotImplementedError()

  @classmethod
  @functools.lru_cache(maxsize=256)
  def FromStrProductNothrow(cls, product, current_datetime=None):
    assert isinstance(product, str), product
    try:
      return cls.FromStrProduct(product, current_datetime)
    except (ValueError, AssertionError) as e:
      return None

  @classmethod
  @functools.lru_cache(maxsize=256)
  def FromStrNativeProductNothrow(cls, product, current_datetime=None):
    assert isinstance(product, str), product
    try:
      return cls.FromStrNativeProduct(product, current_datetime)
    except (ValueError, AssertionError) as e:
      return None

  @classmethod
  @functools.lru_cache(maxsize=256)
  def FromStr(cls, product, current_datetime=None):
    assert isinstance(product, str), product
    errstr = ""
    try:
      return cls.FromStrProduct(product, current_datetime)
    except (ValueError, AssertionError) as e:
      errstr += traceback.format_exc()
    try:
      return cls.FromStrNativeProduct(product, current_datetime)
    except (ValueError, AssertionError) as e:
      errstr += traceback.format_exc()
    raise ValueError("Unable to parse: %s, %s, %s\n%s" % (product, current_datetime, cls, errstr))

  @classmethod
  @functools.lru_cache(maxsize=256)
  def FromStrNothrow(cls, product, current_datetime=None):
    try:
      return cls.FromStr(product, current_datetime)
    except (ValueError, AssertionError) as e:
      return None


class FromStrProductV1V3Mixin(object):
  @classmethod
  def FromStrNativeProductV1(cls, product, current_datetime=None):
    current_datetime = current_datetime or datetime.datetime.now()
    return cls.FromStrNativeProductV1Impl(product, current_datetime)

  @classmethod
  def FromStrNativeProductV3(cls, product, current_datetime=None):
    current_datetime = current_datetime or datetime.datetime.now()
    return cls.FromStrNativeProductV3Impl(product, current_datetime)

  @classmethod
  @functools.lru_cache(maxsize=256)
  def FromStrNativeProductImpl(cls, product, current_datetime):
    try:
      return cls.FromStrNativeProductV1Impl(product, current_datetime)
    except (AssertionError, ValueError):
      pass
    try:
      return cls.FromStrNativeProductV3Impl(product, current_datetime)
    except (AssertionError, ValueError):
      pass
    raise ValueError(product)

  @classmethod
  @abc.abstractmethod
  def FromStrNativeProductV1Impl(cls, product, current_datetime):
    raise NotImplementedError()

  @classmethod
  @abc.abstractmethod
  def FromStrNativeProductV3Impl(cls, product, current_datetime):
    raise NotImplementedError()
